# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: jhkim

import logging
from typing import List, Union
import datetime
import time

from tornado import gen

from coin.exchange.util.async_http_client import AsyncHttpClient
import coin.flow.topic_map as topic_map
from coin.base.param_util import to_list
from coin.exchange.base.ws.subscriber import WebsocketJsonSubscriberBase
from coin.exchange.bithumb.ws.config import group_symbol
from coin.exchange.bithumb.kr_rest.constants import bithumb_cointype_to_name

native_currency_to_bithumb_cointype = {
    value: key for key, value in bithumb_cointype_to_name.items()
}


def _generate_request(ws_id, symbol):
  if ws_id is None:
    return None

  data = {
      "wsId": ws_id,
      "currency": native_currency_to_bithumb_cointype['KRW'],
      "coin": native_currency_to_bithumb_cointype[symbol],
      "tickType": "24H",
      "service": "orderbook|transaction|ticker",
      "csrf_xcoin_name": "defddcb17b920d7fe1bdb1099332cccd",
  }

  headers = {
      'origin':
          'https://www.bithumb.com',
      'user-Agent':
          'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',  # noqa: E501
      "x-requested-with":
          "XMLHttpRequest",
      "content-type":
          "application/x-www-form-urlencoded; charset=UTF-8",
      'cookie':
          'csrf_xcoin_name=defddcb17b920d7fe1bdb1099332cccd',
      "refer":
          "https://www.bithumb.com/trade/order/%s_KRW" % symbol,
      "accept":
          "application/json, text/javascript, */*",
      "accept-encoding":
          "gzip, deflate, br",
      "accept-language":
          "en-US,en;q=0.9",
  }
  request_kwargs = {
      "method": "POST",
      "url": "https://www.bithumb.com/trade/wssend?_=%d" % int(time.time() * 1000),
      "data": data,
      "headers": headers,
  }
  return request_kwargs


class BithumbSubscriber(WebsocketJsonSubscriberBase):
  def __init__(self,
               queue,
               group_id: str,
               worker_id: str,
               symbol: Union[List[str], str],
               close_callback=None,
               logger=None):
    super().__init__(close_callback, logger or logging.getLogger(__name__))
    self._symbol = to_list(symbol)

    self._register_url(
        url="wss://wss.bithumb.com/ws",
        headers={
            'Origin':
                'https://www.bithumb.com',
            'Host':
                'wss.bithumb.com',
            'User-Agent':
                'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',  # noqa: E501
        })
    self._publisher = None
    self._init_publisher(queue, group_id, worker_id)

    self._http_client = AsyncHttpClient(max_connections=10)
    self._ws_id = None

  def _init_publisher(self, queue, group_id: str, worker_id: str):
    topic_depth = topic_map.search_topic_id('bithumb_ws_depth_%s%s' % (group_id, worker_id))[0]
    topic_trade = topic_map.search_topic_id('bithumb_ws_trade_%s%s' % (group_id, worker_id))[0]
    topic_ticker = topic_map.search_topic_id('bithumb_ws_ticker_%s%s' % (group_id, worker_id))[0]
    topic_quote = topic_map.search_topic_id('bithumb_ws_quote_%s%s' % (group_id, worker_id))[0]

    self._publisher = {
        'orderbook': queue.get_writer(topic_id=topic_depth[1]),
        'transaction': queue.get_writer(topic_id=topic_trade[1]),
        'ticker': queue.get_writer(topic_id=topic_ticker[1]),
        'quote': queue.get_writer(topic_id=topic_quote[1]),
        'chrt': None,
        'control': None,
    }

    self._enable_topic_event_publisher(queue,
                                       'bithumb_ws_topic_event', [topic_depth[0], topic_trade[0]])
    self._add_teardown_callback(self._teardown_publisher)

  @gen.coroutine
  def on_open(self):
    self._ioloop.add_timeout(datetime.timedelta(seconds=1), self.subscribe)
    self._publish_topic_event('START')

  @gen.coroutine
  def subscribe(self):
    for symbol in self._symbol:
      request_kwargs = _generate_request(self._ws_id, symbol)
      if request_kwargs is None:
        self._ioloop.add_timeout(datetime.timedelta(seconds=1), self.subscribe)
        return
      else:
        response = yield self._http_client.fetch(**request_kwargs)
        if response.status_code != 200:
          self._logger.error('request failed: %s' % response)
          self._ioloop.add_timeout(datetime.timedelta(seconds=5), self.subscribe)

  def _teardown_publisher(self):
    if self._publisher:
      for _, publisher in self._publisher.items():
        if publisher is not None:
          publisher.close()
      self._publisher = None

  def on_message(self, timestamp: int, message: dict):
    if self.stopped:
      return
    msg_type = message['type']
    if msg_type == 'control' and message['subtype'] == 'session_id':
      self._ws_id = message['content']
      return

    event_type = message['subtype']
    try:
      writer = self._publisher[event_type]
      if writer is not None:
        writer.write(timestamp, message)
    except KeyError:
      self._logger.error('Unknown event_type: %s' % event_type)


class BithumbMultiSubscriberWrapper(object):
  BookFeedDisconnectWindowInNs = 2 * 60 * 1e9  # 2 mins

  def __init__(self,
               queue,
               group_id: str,
               worker_id: str,
               symbols: Union[List[str], str],
               close_callback=None,
               logger=None):
    assert group_id == "TBD"
    self._subscribers = []
    for symbol in symbols:
      group_id = None
      for k, v in group_symbol.items():
        if symbol in v:
          group_id = k
      assert group_id is not None, (symbol, group_id)
      self._subscribers.append(
          BithumbSubscriber(queue, group_id, worker_id, symbol, close_callback, logger))

  def subscribe(self):
    for subscriber in self._subscribers:
      subscriber.subscribe()

  def start(self, *args, **kwargs):
    for subscriber in self._subscribers:
      subscriber.start(*args, **kwargs)

  def stop(self, *args, **kwargs):
    for subscriber in self._subscribers:
      subscriber.stop(*args, **kwargs)
